// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import AppController from './AppScreenPrint';
import PROTOCOL_CONFIG from './config/protocolConfig';
import logger from './Logger';
const Logger = logger;
import Util from './Util';
import NoDebugger from './NoDebugger';


const DEFAULT_RECONNECT_TIMES = 15;
// eslint-disable-next-line no-unused-vars
const INIT_VIRTUAL_DEVICE_STATE = 2048;

/**
 * 生成一个接入实例
 *
 * @param {string} containerId - 用于显示云手机应用的div的id。
 * @param {String} options.ip - 接入云手机使用ip地址。例：127.0.0.1
 * @param {String} options.port - 接入云手机使用端口。例：12345
 * @param {String} options.session_id - 用户本次接入的会话id。客户后台生成的32为uuid，中间无空格、无连接符。
 * @param {String} options.background_timeout - 用户接入时home的时长，用户离开手机界面，home切换到后台。
 * @param {String} options.available_playtime - 用户可以使用的时长，用户本次接入最大可玩时长。
 * @param {String} options.ticket - 256位随机数，客户后台生成的随机数，用于认证。
 * @param {String} options.aes_key - 生成用于本次接入过程中用到的对称密钥。
 * @param {String} options.auth_ts - 生成当前时间的时间戳,用于鉴权模块验签时校验时效性。
 * @param {string} options.user_id - 用户ID
 * @param {string} options.touch_timeout - 无操作超时, 超时后断开连接
 * @param {boolean} options.auto_rotate - 根据屏幕方向自动横竖屏
 * @param {Object} [options.media_config=null] - 多媒体参数
 * @param {boolean} [options.need_heartbeat=true] - 是否需要维持心跳，远程登录phone的场景不需要心跳
 * @param {boolean} [options.reconnect_times=15] - 断链后重连的次数，默认值以保证向前兼容
 */
class StartScreenPrint {
  constructor(containerId, options) {
    let defaults = {};
    this.util = new Util();
    // 参数校验
    let msg = this._verify(options);
    if (msg) {
      throw msg;
    } else {
      this.options = { ...defaults, containerId, ...options };
      this._init_();
    }

    options._debug !== true && NoDebugger.protect();
  }

  static setAppController() {
    const userOptions = this.options;
    let connectURI = userOptions.connect_uri || '';
    /*global _HTTP_MODE_*/
    const WEBSOCKET_PREFIX = _HTTP_MODE_ ? 'ws://' : 'wss://';
    let splitPos = connectURI.startsWith(WEBSOCKET_PREFIX)
      ? WEBSOCKET_PREFIX.length
      : 0;
    connectURI = connectURI.substr(splitPos);
    let options = {
      phoneIp: userOptions.ip,
      phonePort: String(userOptions.port),
      connectURI: connectURI || [userOptions.ip, userOptions.port].join(':'),
      sessionId: String(userOptions.session_id),
      backgroundTimeout: String(userOptions.background_timeout),
      availablePlayTime: String(userOptions.available_playtime),
      ticket: String(userOptions.ticket),
      aesKey: String(userOptions.aes_key),
      authTimeStamp: String(userOptions.auth_ts),
      containerId: userOptions.containerId,
      fullscreenId: userOptions.fullscreenId,
      userId: String(userOptions.user_id || ''),
      /*global __IS_DEBUG__*/
      isDebug: __IS_DEBUG__,
      isMobile: /(iPhone|iPad|iPod|iOS|Android)/i.test(navigator.userAgent),
      supportAudio: true,
      sdkVersion: this.version,
      touchTimeout: String(Math.max(Number(userOptions.touch_timeout || 0), 0)),
      needHeatBeat:
        userOptions.need_heartbeat === undefined
          ? true
          : userOptions.need_heartbeat,
      reconnectTimes:
        userOptions.reconnect_times === undefined
          ? DEFAULT_RECONNECT_TIMES
          : userOptions.reconnect_times,
      libPath: userOptions.libPath,
      microPhoneOutputType: userOptions.microPhoneOutputType,
      keycodeInput:
        userOptions.keycode_input === undefined
          ? true
          : userOptions.keycode_input,
    };
    options.autoRotate = userOptions.auto_rotate && options.isMobile;
    if (userOptions.media_config) {
      options.mediaConfig = userOptions.media_config;
    }
    if (userOptions.decoder_type) {
      options.decoderType = userOptions.decoder_type;
    }

    return new AppController(options);
  }

  _init_() {
    /*global __APP_VERSION__*/
    this.version = __APP_VERSION__;

    this.appController = StartScreenPrint.setAppController.bind(this)();
    this.appController.start();
    window.addEventListener('unload', () => {
      this.exit();
    });
    this.util.bind(window, 'online', () => {
      this.appController.createSocket();
      this.reconnect();
    });
    this.util.bind(window, 'offline', () => {
      this.appController.terminateSocketWorker();
    });
  }

  __defaultValidator(name, val, rule) {
    if (rule.startsWith('len')) {
      let subs = rule.split(':');
      if (subs.length === 2 && val.length !== Number(subs[1])) {
        return `${name}'length is ${subs[1]}.`;
      }

      if (
        subs.length === 3 &&
        (val.length < Number(subs[1]) || val.length > Number(subs[2]))
      ) {
        return `${name}'length is ${subs.splice(1)}.`;
      }
    }

    if (rule.startsWith('fn')) {
      let subs = rule.split(':');
      let fn = subs[1];
      if (!fn || !this[fn]) {
        return '';
      }

      return this[fn](val);
    }

    return '';
  }

  static isSupport() {
    let isSptSocket = window.WebSocket;
    return Boolean(isSptSocket);
  }


  _verify(options) {
    let schema = {
      ip: ['require'],
      port: ['require'],
      session_id: ['require'],
      ticket: ['require'],
      aes_key: ['require', 'hex', 'len:32'],
      auth_ts: ['require'],
      touch_timeout: ['number'],
      media_config: ['fn:_verifyMediaConfig'],
    };
    let msg = '';

    if (!options) {
      return '初始化CloudApp需要一个object类型参数';
    }

    let keys = Object.keys(options);
    for (let i = 0, kLen = keys.length; i < kLen; i++) {
      let name = keys[i];
      let rules = schema[name] || [];
      let val = options[name];
      let subs;
      let error;

      for (let j = 0, rLen = (rules || []).length; j < rLen; j++) {
        switch (rules[j]) {
          case 'require':
            !val && (msg = `${name} is required.`);
            break;
          case 'number':
            isNaN(Number(val)) && (msg = `${name} is a number.`);
            break;
          case 'hex':
            !/^[a-f0-9]*$/i.test(val) && (msg = `${name} is a hex string.`);
            break;
          case 'port':
            (Number(val) < 0 || Number(val) > 65535) &&
              (msg = `${name} is a port.`);
            break;
          case 'ip':
            subs = val.split('.');
            error =
              subs && subs.find(sub => Number(sub) < 0 || Number(sub) > 255);
            (subs.length !== 4 || error) && (msg = `${name} is a ip.`);
            break;
          default:
            msg = this.__defaultValidator(name, val, rules[j]);
            break;
        }

        if (msg) {
          return msg;
        }
      }
    }

    return msg;
  }

  _verifyMediaConfig(config) {
    if (typeof config !== 'object') {
      return 'Media config should be an object.';
    }

    if (
      (config.virtual_width && !config.virtual_height) ||
      (!config.virtual_width && config.virtual_height)
    ) {
      return 'virtual_width and virtual_height should be configured at the same time if you need.';
    }

    let schema = {
      mute: [
        {
          type: 'number',
          valueMap: [0, 1],
        },
      ],
      fps: [
        {
          type: 'number',
          min: 10,
          max: 60,
        },
        {
          type: 'multiple',
          value: 10,
        },
      ],
      bitrate: [
        {
          type: 'number',
          min: 1000000,
          max: 10000000,
        },
      ],
      virtual_width: [
        {
          type: 'number',
          min: 240,
          max: 4096,
        },
        {
          type: 'multiple',
          value: 8,
        },
      ],
      virtual_height: [
        {
          type: 'number',
          min: 240,
          max: 4096,
        },
        {
          type: 'multiple',
          value: 8,
        },
      ],
    };

    const keys = Object.keys(config);
    for (let i = 0, kLen = keys.length; i < kLen; i++) {
      const name = keys[i];
      const val = config[name];
      const rules = schema[name] || [];
      for (let j = 0, rLen = rules.length; j < rLen; j++) {
        let rule = rules[j];
        switch (rule.type) {
          case 'number': {
            let numberVal = Number(val);
            if (isNaN(numberVal)) {
              return `${name} is a number.`;
            }

            if (rule.min !== undefined && numberVal < rule.min) {
              return `${name} should be larger than ${rule.min}.`;
            }

            if (rule.max !== undefined && numberVal > rule.max) {
              return `${name} should be smaller than ${rule.max}.`;
            }

            if (
              rule.valueMap !== undefined &&
              !rule.valueMap.includes(numberVal)
            ) {
              return `${name} must be 0 or 1.`;
            }

            break;
          }

          case 'multiple':
            if (rule.value && Number(val) % rule.value !== 0) {
              return `${name} should be a multiple of ${rule.value}.`;
            }

            break;
          default:
        }
      }
    }

    return '';
  }

  on(eventName, callback) {
    this.appController.on(eventName, callback);
  }

  exit() {
    this.util.unbind(window, 'online');
    this.util.unbind(window, 'offline');
    this.appController.exit();
    this.appController = null;
  }
}

StartScreenPrint.RESOLUTIONS = { ...PROTOCOL_CONFIG.RESOLUTIONS };

export default StartScreenPrint;
